﻿using System;
using System.Diagnostics;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RLProject1.Model.Utility;
using RLProject1.Model.AMEEWorld;
using System.Windows;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Bettzueche.RLLibrary.Util;
using Bettzueche.RLLibrary.Component;
using RLProject1.Model;
using Bettzueche.RLLibrary.Functions;

namespace TestConsole
{
    class Program
    {
        static void runAMEEDraft() {
            Tiling tiling = initTiling();
            AMEEWorldDraft weltKomponente = initWelt(tiling);
            SarsaLambda<WorldImage, AMEEAction> agentKomponente = initAgent(weltKomponente, tiling);

            int counter = 20;
            do {
                Program.ersteLernphase(agentKomponente);

            } while (--counter > 0);
            //do {
            //    Program.printMenü();
            //} while (Program.runProgram(agentKomponente, weltKomponente, tiling));
        }

        static void ersteLernphase(SarsaLambda<WorldImage, AMEEAction> agentKomponente) {
            // ein paar Episoden und anzeigen, wieviele Schriite nätig waren + Endposition
            int anzahlDurchlaufe = 100;
            int maxSteps = 500;
            int stepCount = 0;
            double avgReward = 0.0;
            double avgSteps = 0;
            int minStep = maxSteps;
            double minStepReward = 0.0;
            double maxReward = Double.NegativeInfinity;
            int maxRewardSteps = 0;
            AMEEWorldDraft weltKomponente = (AMEEWorldDraft)agentKomponente.Environment;

            Console.WriteLine("lernen:");
            Console.WriteLine("*****************");
            Console.Write("Episode  ");
            for (int episode = 1; episode <= anzahlDurchlaufe; episode++) {
                stepCount = agentKomponente.RunEpisode(maxSteps);
                avgSteps += stepCount;
                avgReward += agentKomponente.RewardTotal;
                if (stepCount < minStep) {
                    minStep = stepCount;
                    minStepReward = agentKomponente.RewardTotal;
                }
                if (agentKomponente.RewardTotal > maxReward) {
                    maxReward = agentKomponente.RewardTotal;
                    maxRewardSteps = stepCount;
                }

                //Console.WriteLine("Ep {0,4} | S {1,4} | R {2,10:N} | Z {3}", episode, stepCount, agentKomponente.RewardTotal, weltKomponente.IsFinal ? "+" : "-");
                int cl = 8;//= Console.CursorLeft - (((episode - 1) / 10) + 1);
                Console.SetCursorPosition(cl, Console.CursorTop);
                Console.Write(episode);

                stepCount = 0;
                agentKomponente.Reset();
                agentKomponente.Environment.Init(null);
            }
            Console.WriteLine();
            avgSteps /= anzahlDurchlaufe;
            avgReward /= anzahlDurchlaufe;
            Console.WriteLine("Durchschn. Schritte: {0,6:N}\t/\tVergeltung: {1,12:N}", avgSteps, avgReward);
            Console.WriteLine("Schnellste Strecke: {0} Schritte (mit Reward = {1:N})", minStep, minStepReward);
            Console.WriteLine("Beste Strecke: {0:N} Reward (bei {1} Schritten)", maxReward, maxRewardSteps);
            Console.WriteLine();

        }

        static void printMenü() {
            Console.WriteLine(".--------------------.");
            Console.WriteLine("| 200 Episoden: [e]  |");
            Console.WriteLine("| Neustart:     [n]  |");
            Console.WriteLine("| Beenden:      [q]  |");
            Console.WriteLine("`--------------------´");
        }

        static bool runProgram(SarsaLambda<WorldImage, AMEEAction> agentKomponente, AMEEWorldDraft weltKomponente, Tiling tiling) {
            char c = Console.ReadKey().KeyChar;
            bool continu = true;
            switch (c) {
                case 'e':
                    Console.WriteLine();
                    Program.ersteLernphase(agentKomponente);
                    break;
                case 'n':
                    Console.WriteLine();
                    agentKomponente = Program.initAgent(weltKomponente, tiling);
                    break;
                case 'q':
                    Console.WriteLine();
                    continu = false;
                    break;
                default:
                    Console.WriteLine();
                    continu = true;
                    break;
            }
            return continu;
        }

        static Tiling initTiling() {
            int aufteilung_U = 3;                              // Wertebereich [0,1] in 10 Intervalle aufteilen
            int anzSektoren = 4;                               // radiales Weltabbild in 8 Tortenstücke aufteilen
            float[] spurRadien = { 1.0f, 2.0f };  //radiales Weltabbild in 4 Spuren mit je 25 cm Breite aufteilen
            /* ZUVIEL !!
            int aufteilung_U = 10;                              // Wertebereich [0,1] in 10 Intervalle aufteilen
            int anzSektoren = 8;                                // radiales Weltabbild in 8 Tortenstücke aufteilen
            float[] spurRadien = { 0.25f, 0.5f, 0.75f, 1.0f };  //radiales Weltabbild in 4 Spuren mit je 25 cm Breite aufteilen
            */
            Tiling tiling = new Tiling(aufteilung_U, anzSektoren, spurRadien);
            return tiling;
        }

        static AMEEWorldDraft initWelt(Tiling tiling) {
            BumpinessFunction realWelt = HilfsFunktionen.Weltfunktion;
            double XStart = -20.0;
            double YStart = -20.0;
            Vector ziel = new Vector(-20.0, 20.0);
            //IEnvironment<WorldImage, AMEEAction, double> geht nicht, weil Poroperty 'Ziel' fehlt
            AMEEWorldDraft weltKomponente = new AMEEWorldDraft(realWelt, XStart, YStart, tiling);
            weltKomponente.Ziel = ziel;
            return weltKomponente;
        }

        static SarsaLambda<WorldImage, AMEEAction> initAgent(AMEEWorldDraft weltKomponente, Tiling tiling) {

            QFunktion q = new QFunktion(tiling); // IStateValueFunction<WorldImage, AMEEAction>
            GenericExplorationPolicy<AMEEAction> policy = new EpsilonGreedy<AMEEAction>(0.1); // epsilon = 0,1

            SarsaLambda<WorldImage, AMEEAction> agentKomponente = new AMEEAgent(weltKomponente, q, policy);
            agentKomponente.LearnRate = 0.5;        // α
            agentKomponente.DiscountFactor = 0.9;   // γ
            agentKomponente.TraceDecay = 0.9;       // λ

            return agentKomponente;
        }

        static void Main(string[] args)
        {
            runAMEEDraft();

            //meshLaden();

            //testEnumerableRangeRepeat();

            //worldImageInitialisierung();

            //hashSet();

            Console.WriteLine("Press any key to quit");
            Console.ReadKey();
        }

        static void hashSet() {
            HashSet<int> set = new HashSet<int>();
            for (int i = 1; i <= 10; i++) {
                set.Add(i);
            }
            HashSet<int> toRemoveSet = new HashSet<int>();
            foreach (int i in set) {
                Console.Write(i + "->");
                if (i % 2 == 0) {
                    toRemoveSet.Add(i);
                    Console.WriteLine("removed");
                }
                else
                    Console.WriteLine("kept");
            }
            foreach (int item in toRemoveSet) {
                set.Remove(item);
                //set.RemoveWhere(i => toRemoveSet.Contains(i));
            }

            Console.WriteLine();
            foreach (var item in set) {
                Console.Write(item + " ");
            }
            Console.WriteLine();
        }


        static void meshLaden() {
            Mesh mesh = new Mesh();
            Console.WriteLine("Loading Data...");
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            mesh.LoadFromObj(@"data\MeshedReconstruction2.obj");
            stopwatch.Stop();
            TimeSpan ts = stopwatch.Elapsed;
            string elapsedTime = String.Format("{0:00}:{1:00}.{2:000}",
                ts.Minutes, ts.Seconds,
                ts.Milliseconds);
            Console.WriteLine("RunTime " + elapsedTime);

            Console.WriteLine("Writing out into file...");
            string myDocPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
            File.WriteAllText(myDocPath + @"\Meine Rekunstruktion.obj", mesh.ToString());
        }



        static void testEnumerableRangeRepeat() {
            int[] arr1 = Enumerable.Range(1, 5).ToArray();
            Console.WriteLine("[{0}, {1}, {2}, {3}, {4}]", arr1[0], arr1[1], arr1[2], arr1[3], arr1[4]);

            arr1 = Enumerable.Repeat<int>(1, 5).ToArray();
            Console.WriteLine("[{0}, {1}, {2}, {3}, {4}]", arr1[0], arr1[1], arr1[2], arr1[3], arr1[4]);

            double[] arr2 = Enumerable.Range(1, 5).Select<int, double>(i => Math.Pow(Math.E, i)).ToArray();
            Console.WriteLine("[{0}, {1}, {2}, {3}, {4}]", arr2[0], arr2[1], arr2[2], arr2[3], arr2[4]);
        }



        static void worldImageInitialisierung() {
            var tiling = new Tiling(2, 2, new float[] { 1f, 2f });
            var testObject = new WorldImage(tiling, 2, 3, (x, y) => x - y);
            //var testObject = new WorldImage(2, 2, new float[] { 1f, 2f }, 2, 3, (x, y) => x - y);
            Vector[] messPunkte = testObject.GetMesspunkte();

            try {
                Assert.IsNotNull(messPunkte, "GetMesspunkte() liefert null");
            }
            catch (AssertFailedException ex) {
                Console.WriteLine("FAIL Is Not Null. " + ex.Message);
                return;
            }


            int anzahlErwartet = 24;
            try {
                Assert.AreEqual<int>(anzahlErwartet, messPunkte.Length, "Anzahl der Messpunkte stimmt nicht. Soll: {0}, Ist: {1}", anzahlErwartet, messPunkte.Length);
            }
            catch (AssertFailedException ex) {
                Console.WriteLine("FAIL Are Equal. " + ex.Message);
                return;
            }

            double produktX = 1;
            for (int i = 0; i < 6; i++) {
                produktX *= messPunkte[i].X;
            }
            for (int i = 12; i < 18; i++) {
                produktX *= messPunkte[i].X;
            }
            try {
                Assert.IsTrue((produktX != 0), "Ein X-Wert ist 0!");
            }
            catch (AssertFailedException ex) {
                Console.WriteLine("FAIL Is True produktX != 0. " + ex.Message);
                Console.WriteLine(VectorArrayToString(messPunkte));
                return;
            }
            try {
                Assert.IsTrue((produktX > 0), "Anordnung der Messpunkte nicht wie erwartet. Sektor 0 X-Werte sollten alle positiv sein.");
            }
            catch (AssertFailedException ex) {
                Console.WriteLine("FAIL Is True produktX > 0. " + ex.Message);
                Console.WriteLine(messPunkte.ToString());
                return;
            }
            Console.WriteLine(VectorArrayToString(messPunkte));
        }

        static string VectorArrayToString(Vector[] arr) {
            string resu = "[" + String.Join(" | ", arr.Select(v => v.ToString()).ToArray()) + "]";
            return resu;
        }
    }
}
